home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1997 / MacHack 1997.toast / Presentations / Presentations ’96 / Papers ’96 / Book of Practical Objects / BasicObjects ƒ / CKeywords.cp < prev    next >
Encoding:
Text File  |  1996-06-03  |  12.2 KB  |  516 lines  |  [TEXT/CWIE]

  1. #include "CKeywords.h"
  2. #include "BoyerMoore.h"
  3.  
  4.     CKeywords::CKeywords(short longsPerReferenceItem, Boolean reuseHoles)
  5. {
  6.     fLongsPerReferenceItem = longsPerReferenceItem;
  7.     fReuseEmptyHoles = reuseHoles;
  8.     
  9.     fTotalKeywords = 0;
  10.     fFirstKeywordBlock = nil;
  11.  
  12.     fReferenceBlockSize = longsPerReferenceItem * sizeof(long);
  13. }
  14.  
  15.  
  16.     CKeywords::~CKeywords(void)
  17. {
  18.     // Delete all the keyword pages
  19.     if (fFirstKeywordBlock != nil)
  20.     {
  21.         DeleteAllKeywords();
  22.     }
  23. }
  24.  
  25.  
  26. long    CKeywords::GetKeywordCount(void)
  27. {
  28.     return (fTotalKeywords);
  29. }
  30.  
  31.  
  32. long    CKeywords::GetKeywordReferenceCount(long keywordID)
  33. {
  34.  
  35. }
  36.  
  37.  
  38. long    CKeywords::GetKeywordReferenceCount(char* theKeyword, short keyLength, Boolean partialMatch)
  39. {
  40.     long    theID;
  41.     long    refCount;
  42.     
  43.     theID = FindKeywordID(theKeyword, keyLength, partialMatch);
  44.     if (theID > 0)
  45.         refCount = GetKeywordReferenceCount(theID);
  46.     else
  47.         refCount = 0;
  48.     
  49.     return (refCount);
  50. }
  51.  
  52.  
  53. long    CKeywords::FindKeywordID(char* theKeyword, short keyLength, Boolean partialMatch)
  54. {
  55.     BoyerMoore    *theSearcher;
  56.     KeywordPageH    workPage = fFirstKeywordBlock;
  57.     long        foundID = -1;
  58.     long        dataLen;
  59.     Ptr            searchStartP;
  60.     long        foundOffset;
  61.     long        testBlock;
  62.  
  63.     theSearcher = new BoyerMoore(theKeyword, keyLength);
  64.     
  65.     while ((workPage != nil) && (foundID == -1))
  66.     {
  67.         HLock((Handle) workPage);
  68.         
  69.         dataLen = kMaxKeywordsPerPage * sizeof(KeywordHolder);
  70.         foundOffset = 0;    // Special case for loop start
  71.         do {
  72.             dataLen -= foundOffset;
  73.             searchStartP = (Ptr) (*workPage)->keywords;
  74.             theSearcher->SetSearchData(searchStartP + foundOffset, dataLen);
  75.             foundOffset = theSearcher->Search();
  76.             if (foundOffset > 0)        // Figure the block we were in
  77.             {
  78.                 testBlock = foundOffset / sizeof(KeywordHolder);
  79.                 // Now we need to determine if this is the start of a block
  80.                 // We can test the high byte of blockCount and it it is
  81.                 // non-zero this block is all text. (A NULL in the middle of
  82.                 // a keyword is considered bad form.)
  83.                 while ((foundID == -1) && (testBlock >= 0))
  84.                 {
  85.                     if (((*workPage)->keywords[testBlock].blockCount && 0xFF00) != 0)    // OK it was bad
  86.                         testBlock--;
  87.                     else {
  88.                             foundID = testBlock;
  89.                             // Now do we meet the partialMatch criteria?
  90.                             if (partialMatch == false)
  91.                             {
  92.                                 // Instead of comparing the whole string, I can
  93.                                 // get away with comparing the lengths and if they
  94.                                 // are the same it is the whole match. (Unless
  95.                                 // the search string matched the next block by some
  96.                                 // freak of the universe and there were probably
  97.                                 // nulls in it!)
  98.                                 if ((*workPage)->keywords[testBlock].length != keyLength)
  99.                                 {
  100.                                     // Didn't match partialMatch. Set to the start of
  101.                                     // the next block and keep going
  102.                                     foundOffset = (testBlock + 1) * sizeof(KeywordHolder) - 1;
  103.                                     foundID = -1;
  104.                                 }
  105.                             }
  106.                             
  107.                          }
  108.                 }
  109.                 foundOffset++;
  110.             }
  111.         }
  112.         while ((foundOffset > 0) && (foundID == -1));
  113.         HUnlock((Handle) workPage);
  114.         
  115.         if (foundID == -1)    // Go to the next page
  116.             workPage = (*workPage)->nextPage;
  117.     }
  118.     
  119.     if (workPage == nil)
  120.         return (0);
  121.     else
  122.         return ((*workPage)->firstKeywordID + foundID + 1);
  123. }
  124.  
  125.         
  126. long    CKeywords::FindKeywordIDInRange(long startID, long endID, char* theKeyword, short keyLength, Boolean partialMatch)
  127. {
  128.  
  129. }
  130.  
  131.  
  132. short    CKeywords::GetKeywordByID(long keywordID, char* keyOut)
  133. {
  134.     long    whichPage;
  135.     long    whichItem;
  136.     KeywordPageH    workPage;
  137.     KeywordHolderP    workKeywordP;
  138.     short    length;
  139.     Boolean    gotPage;
  140.  
  141.     keywordID -= 1;        // Zero base for internal use
  142.     
  143.     whichPage = keywordID / kMaxKeywordsPerPage;
  144.  
  145.     gotPage = GetPageN(whichPage, &workPage, false);
  146.     if (gotPage == false)            // Whoops, the ID is out of range!
  147.         return (0);
  148.  
  149.     whichItem = keywordID % kMaxKeywordsPerPage;
  150.     
  151.     // Now we have the specified page in workPage
  152.     // Notice that we are NOT locking the page so if we
  153.     // do something stupid like moving memory workKeywordP
  154.     // could end up really invalid
  155.     workKeywordP = &(*workPage)->keywords[whichItem];
  156.     length = workKeywordP->length;
  157.     if ((length > 0) && (keyOut != nil))
  158.     {
  159.         // If block move moves relocatable blocks around under us
  160.         // the entire OS will die, so this is safe
  161.         // And we use BlockMoveData to prevent cache flushing on
  162.         // some machines (68040 specifically)
  163.         BlockMoveData(workKeywordP->keywordData, keyOut, length);
  164.     }
  165.  
  166.     return (length);
  167. }
  168.  
  169.  
  170. // The following routines are used to add keywords
  171. long    CKeywords::AddKeyword(char* theKeyword, short keyLength)
  172. {
  173.     short    requiredBlocks = 1;        // Always need at least one
  174.     short    tempLength;
  175.     KeywordPageH    outPage;
  176.     short            pageIndex;
  177.     KeywordHolderP    keywordP;
  178.     long    theID;
  179.     short    pageNumber;
  180.     
  181.     tempLength = keyLength - kCharsInFirstKeywordlock;
  182.     while (tempLength > 0)
  183.     {
  184.         requiredBlocks++;
  185.         tempLength -= sizeof(KeywordHolder);    // Subtracting size of extra blocks
  186.     }
  187.  
  188.     FindAvailableSlot(requiredBlocks, &outPage, &pageIndex, &pageNumber);
  189.     if (pageIndex >= 0)
  190.     {
  191.         // Now I know where to store the keyword, so I need to save it now.
  192.         keywordP = &(*outPage)->keywords[pageIndex];
  193.         keywordP->blockCount = requiredBlocks;
  194.         keywordP->length = keyLength;
  195.         BlockMove(keywordP->keywordData, theKeyword, keyLength);
  196.         
  197.         // Now I just need to update the block map
  198.         UpdateBlockMap(outPage, pageIndex, requiredBlocks, true);
  199.     }
  200.     else
  201.         return (-1);
  202.     
  203.     fTotalKeywords++;
  204.  
  205.     // Now figure out what the ID is
  206.     theID = (pageNumber * kMaxKeywordsPerPage) + pageIndex + 1;
  207.     return (theID);
  208. }
  209.  
  210.  
  211. Boolean    CKeywords::AddKeywordReference(long keywordID, long* keyReferenceBlock,
  212.                                 short referenceCount)
  213. {
  214.  
  215. }
  216.  
  217.  
  218. void    CKeywords::AddReferenceToAllKeywords(long *keyReferenceBlock);
  219.  
  220. // And of course you might want to delete a keyword
  221. void    CKeywords::DeleteKeywordID(long keywordID)
  222. {
  223.     long            whichPage;
  224.     long            whichItem;
  225.     Boolean            gotPage;
  226.     KeywordPageH    workPage;
  227.     short            count;
  228.  
  229.     keywordID -= 1;        // Zero base for internal use
  230.     
  231.     whichPage = keywordID / kMaxKeywordsPerPage;
  232.  
  233.     gotPage = GetPageN(whichPage, &workPage, false);
  234.     if (gotPage == false)            // Whoops, the ID is out of range!
  235.         return;
  236.  
  237.     whichItem = keywordID % kMaxKeywordsPerPage;
  238.     
  239.     count = (*workPage)->keywords[whichItem].blockCount;
  240.     if (fReuseEmptyHoles)
  241.         UpdateBlockMap(workPage, whichItem, count, false);
  242.     
  243.     // Now delete the references for this keyword
  244. }
  245.  
  246.  
  247. void    CKeywords::DeleteKeywordReference(long keywordID, long* keyReferenceBlock)
  248. {
  249.  
  250. }
  251.  
  252.  
  253. void    CKeywords::DeleteAllReferenceMatch(long *keyReferenceBlock)
  254. {
  255.  
  256. }
  257.  
  258.  
  259. void    CKeywords::DeleteAllReferences(void)
  260. {
  261.  
  262. }
  263.  
  264.  
  265. // Use this with extreme caution! It nukes references, keyword, everything
  266. void    CKeywords::DeleteAllKeywords(void)
  267. {
  268.     KeywordPageH    nextHandle = fFirstKeywordBlock;
  269.     KeywordPageH    currentHandle;
  270.     
  271.     while (nextHandle != nil)
  272.     {
  273.         currentHandle = nextHandle;
  274.         nextHandle = (*(KeywordPageH)currentHandle)->nextPage;
  275.         DisposeHandle((Handle) currentHandle);
  276.     }
  277.     
  278.     fFirstKeywordBlock = nil;
  279. }
  280.  
  281.  
  282. KeywordPageH    CKeywords::CreateEmptyPage(long firstID)
  283. {
  284.     KeywordPageH    thePage;
  285.     
  286.     thePage = (KeywordPageH) NewHandleClear(sizeof(KeywordPage));
  287.     if (thePage != nil)
  288.     {
  289.         (*thePage)->firstKeywordID = firstID;
  290.         // Every other field has been zeroed when it was created
  291.     }
  292.  
  293.     return (thePage);
  294. }
  295.  
  296.  
  297. Boolean    CKeywords::GetPageN(short whichPage, KeywordPageH *outHand, Boolean createIt)
  298. {
  299.     KeywordPageH    currentPage = fFirstKeywordBlock;
  300.     KeywordPageH    nextPage = fFirstKeywordBlock;
  301.     
  302.     while (whichPage > 0)
  303.     {
  304.         if (currentPage != nil)
  305.         {
  306.             nextPage = (*currentPage)->nextPage;
  307.             // Now nextPage doesn't exist yet, if whichPage is 1 and 
  308.             // and createIt is true, then make a next page
  309.             // otherwise, we go back empty handled
  310.             if (nextPage == nil)
  311.             {
  312.                 if ((whichPage == 1) && (createIt))
  313.                 {
  314.                     nextPage = CreateEmptyPage(whichPage * kMaxKeywordsPerPage);
  315.                     (*currentPage)->nextPage = nextPage;
  316.                 }
  317.                 else {
  318.                         // bad news. We can't or won't create the page
  319.                         currentPage = nil;
  320.                         break;
  321.                      }
  322.             }
  323.         }
  324.         else
  325.             break;    // Leave the loop NOW if currentPage is nil
  326.         
  327.         currentPage = nextPage;
  328.         whichPage--;
  329.     }
  330.  
  331.     *outHand = currentPage;
  332.     
  333.     return (currentPage != nil);
  334. }
  335.  
  336.  
  337. void    CKeywords::FindAvailableSlot(short requiredBlocks, KeywordPageH *outPage,
  338.                                         short *pageIndex, short *pageNumber)
  339. {
  340.     KeywordPageH    currentPage;
  341.     KeywordPageH    nextPage;
  342.     Boolean            madeFirstPage = false;    // Special case for empty object
  343.     Boolean            foundSlot;
  344.     short            targetBlock = -1;
  345.     short            whichPage = 0;
  346.  
  347.     if (fFirstKeywordBlock == nil)
  348.     {
  349.         madeFirstPage = true;
  350.     }
  351.     
  352.     nextPage = fFirstKeywordBlock;
  353.     foundSlot = false;
  354.  
  355.     while (!foundSlot)
  356.     {
  357.         if (nextPage == nil)
  358.         {
  359.             nextPage = CreateEmptyPage(0);
  360.             if (nextPage == nil)        // Bad things have just happened.
  361.             {
  362.                 *outPage = nil;
  363.                 *pageIndex = -1;
  364.             }
  365.             else {
  366.                     // OK, we made a page, better save it to the list
  367.                     if (madeFirstPage)
  368.                         fFirstKeywordBlock = nextPage;
  369.                     else {
  370.                             (*currentPage)->nextPage = nextPage;
  371.                          }
  372.  
  373.                     if (requiredBlocks > kMaxKeywordsPerPage) // IT BETTER NOT BE
  374.                      {
  375.                          *outPage = nil;
  376.                          *pageIndex = -1;
  377.                      }
  378.                     else {
  379.                             
  380.                             *outPage = nextPage;
  381.                             *pageIndex = 0;
  382.                          }
  383.                  }
  384.             foundSlot = true;    // One way or another we are done now
  385.         }
  386.         
  387.         currentPage = nextPage;
  388.         if (currentPage != nil)
  389.             nextPage = (*currentPage)->nextPage;
  390.         
  391.         // OK, now let's see if there is a free space in this page
  392.         targetBlock = FindSlotsOnPage(currentPage, requiredBlocks);
  393.         if (targetBlock > 0)
  394.         {
  395.             foundSlot = true;
  396.         }
  397.         else        // Update the page if this doesn't fit on the page
  398.             whichPage++;
  399.     }
  400.     
  401.     // Now currentPage has been setup as NIL (unfound) or a legitimate page
  402.     *outPage = currentPage;
  403.     *pageIndex = targetBlock;
  404.     *pageNumber = whichPage;
  405. }
  406.  
  407.  
  408. short    CKeywords::FindSlotsOnPage(KeywordPageH currentPage, short blocksRequired)
  409. {
  410.     // THIS IS VERY INEFFICIENT FOR NOW, BUT IT WORKS. THIS SHOULD
  411.     // BE CHANGED TO A FASTER ALGORITHM IN THE NEAR FUTURE
  412.     long    limit;
  413.     long    i, j;
  414.     long    theBit;
  415.     unsigned long    mask;
  416.     short    blocksStillToMatch;
  417.     short    potentialSite = -1;    // Zero based into they keyword map
  418.     long    bitsInThisLong;
  419.     unsigned long    currentLong;
  420.  
  421.     limit = (kMaxKeywordsPerPage / kBitsInALong) + ( (kMaxKeywordsPerPage % kBitsInALong) ? 1 : 0);
  422.  
  423.     blocksStillToMatch = blocksRequired;
  424.     for (i = 0; i < limit; i++)
  425.     {
  426.         // i is the long we are currently testing.
  427.         // theBit is the actual bit (up to kMaxKeywordsPerPage) we are testing
  428.         mask = 0x80000000;    // Reset this at the start of each long
  429.         currentLong = (*currentPage)->blockMap[i];
  430.         
  431.         if (i == (limit - 1))    // On the last long, may be fewer bits
  432.             bitsInThisLong = kMaxKeywordsPerPage % kBitsInALong;
  433.         else
  434.             bitsInThisLong = kBitsInALong;
  435.  
  436.         for (j = 0; j < bitsInThisLong; j++, mask >>= 1)    // Bits per long
  437.         {
  438.             if ((currentLong & mask) == 0)    // Hey this is an empty hole!
  439.             {
  440.                 blocksStillToMatch--;
  441.                 potentialSite = (i * kBitsInALong) + j;
  442.             }
  443.             else {
  444.                     blocksStillToMatch = blocksRequired;
  445.                     potentialSite = -1;
  446.                  }
  447.  
  448.             if (blocksStillToMatch == 0)    // Success!!!
  449.             {
  450.                 j = kBitsInALong;    // This is always good, even on the last partial long
  451.                 i = limit;
  452.                 
  453.                 // And potentialSite has the first bit/block to set
  454.             }
  455.         }
  456.     }
  457.     
  458.     if (blocksStillToMatch == 0)    // Ran out of page to search when we
  459.         potentialSite = -1;            // were actually matching!
  460.  
  461.     return (potentialSite);
  462. }
  463.  
  464.  
  465. void    CKeywords::UpdateBlockMap(KeywordPageH thePage, short firstBlock, short count, Boolean turnOn)
  466. {
  467.     short    whichLong = firstBlock / kBitsInALong;
  468.     short    firstBit = firstBlock % kBitsInALong;
  469.     unsigned long    mask;
  470.     short    workCount;
  471.     
  472.     workCount = count;
  473.     mask = 0;
  474.  
  475.     while (workCount > 0)
  476.     {
  477.         mask = (mask >> 1) || 0x80000000;    // Set the bits in the mask
  478.         workCount--;
  479.     }
  480.  
  481.     mask >>= firstBit;    // now move the mask into position
  482.     if (turnOn == false)
  483.     {
  484.         mask = ~mask;    // Invert the entire mask
  485.         (*thePage)->blockMap[whichLong] & mask;    // This clears it OK
  486.     }
  487.     else {
  488.             (*thePage)->blockMap[whichLong] | mask;    // This sets the bits
  489.          }
  490.  
  491.     workCount = (firstBit + (count - 1)) - kBitsInALong;
  492.     if (workCount > 0)
  493.     {
  494.         // Nuts we have a block that rolls over more than 1 byte
  495.         // How many bits do we need set now? (It is in workCount)
  496.         whichLong++;        // Go to the next word
  497.         mask = 0;
  498.         while (workCount > 0)
  499.         {
  500.             mask = (mask >> 1) || 0x80000000;    // Set the bits in the mask
  501.             workCount--;
  502.         }
  503.  
  504.         if (turnOn == false)
  505.         {
  506.             mask = ~mask;    // Invert the entire mask
  507.             (*thePage)->blockMap[whichLong] & mask;    // This clears it OK
  508.         }
  509.         else {
  510.                 (*thePage)->blockMap[whichLong] | mask;    // This sets the bits
  511.              }
  512.  
  513.     }
  514. }
  515.  
  516.